/*
*
==============================================================================
*  Name        : 
*  Part of     : Omen Game Engine
*  Description : 
*  Version     : 1.0
*
*  Copyright (c) 2013 Lauri Kortevaara.
* 
==============================================================================
*/

#define OMEN_SOURCE 1

/// Omen Headers
#include "OmenModel.h"
#include <OmenUtils.h>

/// STL Headers
#include <iostream>

using namespace Omen;

/// <summary>Default constructor of Omen Model</summary>
Model::Model() :
    m_boundingVolume(NULL),
    m_pScene(NULL),
    m_scale(1,1,1),
    m_bUpdateNeeded(true)
{
}

/// <summary>Copy constructor of Omen Model</summary>
Model::Model( const Model& other ):
    m_boundingVolume(NULL),
    m_bUpdateNeeded(true)
{
    m_scale = other.m_scale;
    m_meshes = other.m_meshes;
}

/// <summary>Destructor of Omen Model</summary>
Model::~Model()
{
    // Deallocate meshes
    for( unsigned long i=0; i < m_meshes.size(); ++i )
        delete m_meshes[i];
    m_meshes.clear();

    if( m_boundingVolume )
        delete m_boundingVolume;
    m_boundingVolume = NULL;
}

void Model::setScene( Omen::IScene* pScene ) 
{ 
    m_pScene = pScene; 
}

Omen::IScene* Model::scene() const 
{ 
    return m_pScene; 
}

/// <summary>returns the mesh count in the model</summary>
unsigned long Model::meshCount() const
{
    return m_meshes.size();
}

/// <summary>adds a new mesh in the model</summary>
void Model::addMesh( IMesh* pMesh )
{
    if( pMesh )
    {
        pMesh->setModel(this);
        m_meshes.push_back( pMesh );
    }
}


/// <summary>returns the meshes in the model</summary>
void Model::getMeshes( IMesh** pMeshList, unsigned long& maxSize )
{
    maxSize = min(maxSize,m_meshes.size());

    for( unsigned long i = 0; i < maxSize; ++i )
    {
        pMeshList[i] = m_meshes.at(i);
    }
}

Omen::IMesh* Model::getMesh( unsigned long index )
{
    if( index < m_meshes.size() )
        return m_meshes.at(index);
    else
        return NULL;
}
    
bool  Model::testCollision( Omen::ICollidable** pListObjects, unsigned long maxNum )
{
    bool bCollides = false;
    for( unsigned long i = 0; i < maxNum; ++i )
    {
        ICollidable* pObj = pListObjects[i];
        if( pObj )
        {
                        
        }
    }
    return bCollides;
}

void Model::setTransformation( const Omen::Matrix4x4& matrix ) 
{ 
    m_transformation = matrix; 
}

Omen::Matrix4x4 const& Model::transformation() const 
{ 
    return m_transformation; 
}

void Model::setScale( const Omen::Vector3d& scale )
{
    m_scale = scale;
    for( unsigned long i = 0; i < m_meshes.size(); ++i )
        m_meshes[i]->setScale(m_scale.x, m_scale.y, m_scale.z);
}
Omen::Vector3d Model::scale() const
{
    return m_scale;
}

void Model::setProperty( const std::wstring& propertyName, const std::wstring& propertyValue )
{
    IObject::setProperty(propertyName, propertyValue);

    if( propertyName == L"position" )
    {
        std::vector<Omen::Float> v;
        getFloatsVector( propertyValue, v );
        if( v.size() == 3 )
        {
            m_transformation.setRow(3, Vector3d( v[0], v[1], v[2] ) );
        }
    }
    if( propertyName == L"scale" )
    {
        std::vector<Omen::Float> v;
        getFloatsVector( propertyValue, v );
        if( v.size() == 3 )
        {
            setScale( Vector3d( v[0], v[1], v[2] ) );
        }
    }
}

/// <summary>Returns the axis aligned bounding box of the model</summary>
const Omen::AABB& Model::boundingBox() const
{
    return m_boundingBox;
}

/// <summary>Returns the bounding sphere of the model</summary>
const Omen::Sphere& Model::boundingSphere() const
{
    return m_boundingSphere;
}

/// <summary>Returns the center point of this model in global 3d coordinate space</summary>
const Omen::Vector3d& Model::center()
{
    Omen::Vector3d vCenter = Omen::Vector3d(0.0,0.0,0.0);
    if( meshCount() )
    for( unsigned long iMesh = 0; iMesh < meshCount(); iMesh++ )
    {
        Omen::IMesh* pMesh = m_meshes.at(iMesh);
        if( pMesh )
        {
            vCenter += pMesh->center();
        }
        vCenter /= static_cast<Omen::Float>(meshCount());
    }
    
    vCenter += m_transformation.row(3); // Translate the center point by this models origo
    m_center = vCenter;

    return m_center;
}

void Model::setUpdateNeeded( bool bNeeded )
{
    m_bUpdateNeeded = bNeeded;
}

void Model::updateBoundingVolume()
{
    if( m_bUpdateNeeded )
    {
        /*
        std::wstring modelName;
        getProperty( L"name", modelName );
        std::wcout << L"Updating model " << modelName << L"'s boundingVolume." << std::endl;
        */
        m_boundingSphere = Omen::Sphere();
        m_boundingBox = Omen::AABB();
        m_boundingBox.setMinimum(Vector3d(1.0e10, 1.0e10, 1.0e10) );
        m_boundingBox.setMaximum(Vector3d(-1.0e10, -1.0e10, -1.0e10) );

        for( auto iter = m_meshes.begin(); iter != m_meshes.end(); ++iter )
        {
            IMesh* pMesh = *iter;
            if( pMesh )
            {
                pMesh->updateBoundingVolumes();
                m_boundingSphere = m_boundingSphere + pMesh->boundingSphere();
                m_boundingBox = m_boundingBox + pMesh->boundingBox();
            }
        }

        Omen::Float minX = m_boundingBox.minimum().x;
        Omen::Float minY = m_boundingBox.minimum().y;
        Omen::Float minZ = m_boundingBox.minimum().z;
        Omen::Float maxX = m_boundingBox.maximum().x;
        Omen::Float maxY = m_boundingBox.maximum().y;
        Omen::Float maxZ = m_boundingBox.maximum().z;
        //std::cout << "Model Minimum aabb : (" << minX << ", " << minY << ", " << minZ << ")" << std::endl;
        //std::cout << "Model Maximum aabb : (" << maxX << ", " << maxY << ", " << maxZ << ")" << std::endl;

        //Omen::Vector3d origo = m_transformation.row(3);
        //m_boundingSphere.setPosition( origo );
        m_boundingBox.setMaximum( m_boundingBox.maximum()/*+origo*/ );
        m_boundingBox.setMinimum( m_boundingBox.minimum()/*+origo*/ );
        
        m_bUpdateNeeded = false;
    }
}